﻿// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
//     <version>$Revision$</version>
// </file>

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;

namespace ICSharpCode.TextEditor.Document
{
	/// <summary>
	/// This class manages the selections in a document.
	/// </summary>
	public class SelectionManager : IDisposable
	{
        bool mutilSelect = false;

        int selectionMaxLine = 0;
        int selectionMaxCloumn = 0;
 
        TextArea textArea;
        IDocument document;
        TextLocation selectionStart;
        TextLocation selectionEnd;
        TextLocation selectionMax;

        internal int DrawLineX1pos = 0;
        internal int DrawLineX2pos = 0;
        internal int DrawLineY1pos = 0;
        internal int DrawLineY2pos = 0;

        internal TextLocation SelectionStart {
			get { return selectionStart; }
			set {
				DefaultDocument.ValidatePosition(document, value);
				selectionStart = value;
			}
		}

        internal TextLocation SelectionEnd
        {
            get { return selectionEnd; }
            set { selectionEnd = value; }
        }

        internal TextLocation SelectionMax
        {
            get { return new TextLocation(selectionMaxCloumn, selectionMaxLine); }            
        }

        internal SelectFrom selectFrom = new SelectFrom();

		internal List<ISelection> selectionCollection = new List<ISelection>();
		
		/// <value>
		/// A collection containing all selections.
		/// </value>
		public List<ISelection> SelectionCollection {
			get {
				return selectionCollection;
			}
		}
		
		/// <value>
		/// true if the <see cref="SelectionCollection"/> is not empty, false otherwise.
		/// </value>
		public bool HasSomethingSelected {
			get {
				return selectionCollection.Count > 0;
			}
		}

        public bool HasSomethingMutilLinesSeleted
        {
            get
            {
                if (mutilSelect && selectionCollection.Count > 0)
                {
                    return selectionEnd.Column > selectionStart.Column;
                }
                return false;
            }
        }

        public bool IsMutilSelect
        {
            get
            {
                return mutilSelect;
            }
            internal set
            {
                mutilSelect = value;
            }
        }

        public bool SelectionIsReadonly {
			get {
				if (document.ReadOnly)
					return true;
				foreach (ISelection sel in selectionCollection) {
					if (SelectionIsReadOnly(document, sel))
						return true;
				}
				return false;
			}
		}
		
		internal static bool SelectionIsReadOnly(IDocument document, ISelection sel)
		{
			if (document.TextEditorProperties.SupportReadOnlySegments)
				return document.MarkerStrategy.GetMarkers(sel.Offset, sel.Length).Exists(m=>m.IsReadOnly);
			else
				return false;
		}
		
		/// <value>
		/// The text that is currently selected.
		/// </value>
		public string SelectedText
        {
            get
            {
                StringBuilder builder = new StringBuilder();
                if (mutilSelect)
                {
                    foreach (ISelection s in selectionCollection)
                    {
                        builder.Append(s.SelectedText);                        
                    }
                }
                else
                {
                    foreach (ISelection s in selectionCollection)
                    {
                        if (s.IsCurrent)
                        {
                            builder.Append(s.SelectedText);
                        }
                    }
                }               

                return builder.ToString();
            }
        }
		
		/// <summary>
		/// Creates a new instance of <see cref="SelectionManager"/>
		/// </summary>
		public SelectionManager(IDocument document)
		{
			this.document = document;
			document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
		}

		/// <summary>
		/// Creates a new instance of <see cref="SelectionManager"/>
		/// </summary>
		public SelectionManager(IDocument document, TextArea textArea)
		{
			this.document = document;
			this.textArea = textArea;
			document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
		}

		public void Dispose()
		{
			if (this.document != null) {
				document.DocumentChanged -= new DocumentEventHandler(DocumentChanged);
				this.document = null;
			}
		}
		
		void DocumentChanged(object sender, DocumentEventArgs e)
		{
			if (e.Text == null) {
				Remove(e.Offset, e.Length);
			} else {
				if (e.Length < 0) {
					Insert(e.Offset, e.Text);
				} else {
					Replace(e.Offset, e.Length, e.Text);
				}
			}
		}

        /// <remarks>
        /// Clears the selection and sets a new selection
        /// using the given <see cref="ISelection"/> object.
        /// </remarks>
        public void SetSelection(ISelection selection)
        {
            //			autoClearSelection = false;
            if (selection != null)
            {
                selection.SetSelectIsCurrent(true);
                if (SelectionCollection.Count == 1 &&
                    selection.StartPosition == SelectionCollection[0].StartPosition &&
                    selection.EndPosition == SelectionCollection[0].EndPosition)
                {
                    return;
                }
                ClearWithoutUpdate();
                selectionCollection.Add(selection);
                document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, selection.StartPosition.Y, selection.EndPosition.Y));
                document.CommitUpdate();
                OnSelectionChanged(EventArgs.Empty);
            }
            else
            {
                ClearSelection();
            }
        }
		
		public void SetSelection(TextLocation startPosition, TextLocation endPosition)
		{
			SetSelection(new DefaultSelection(document, startPosition, endPosition));
		}
		
		public bool GreaterEqPos(TextLocation p1, TextLocation p2)
		{
			return p1.Y > p2.Y || p1.Y == p2.Y && p1.X >= p2.X;
		}
		
		public void ExtendSelection(TextLocation oldPosition, TextLocation newPosition)
		{
			// where oldposition is where the cursor was,
			// and newposition is where it has ended up from a click (both zero based)

			if (oldPosition == newPosition)
			{
				return;
			}

			TextLocation min;
			TextLocation max;
			int oldnewX = newPosition.X;
			bool  oldIsGreater = GreaterEqPos(oldPosition, newPosition);
			if (oldIsGreater) {
				min = newPosition;
				max = oldPosition;
			} else {
				min = oldPosition;
				max = newPosition;
			}

			if (min == max) {
				return;
			}

			if (!HasSomethingSelected)
			{
				SetSelection(new DefaultSelection(document, min, max));
				// initialise selectFrom for a cursor selection
				if (selectFrom.where == WhereFrom.None)
					SelectionStart = oldPosition; //textArea.Caret.Position;
				return;
			}

			ISelection selection = this.selectionCollection[0];

			if (min == max) {
				//selection.StartPosition = newPosition;
				return;
			} else {
				// changed selection via gutter
				if (selectFrom.where == WhereFrom.Gutter)
				{
					// selection new position is always at the left edge for gutter selections
					newPosition.X = 0;
				}

				if (GreaterEqPos(newPosition, SelectionStart)) // selecting forward
				{
					selection.StartPosition = SelectionStart;
					// this handles last line selection
					if (selectFrom.where == WhereFrom.Gutter ) //&& newPosition.Y != oldPosition.Y)
						selection.EndPosition = new TextLocation(textArea.Caret.Column, textArea.Caret.Line);
					else {
						newPosition.X = oldnewX;
						selection.EndPosition = newPosition;
					}
				} else { // selecting back
					if (selectFrom.where == WhereFrom.Gutter && selectFrom.first == WhereFrom.Gutter)
					{ // gutter selection
						selection.EndPosition = NextValidPosition(SelectionStart.Y);
					} else { // internal text selection
						selection.EndPosition = SelectionStart; //selection.StartPosition;
					}
					selection.StartPosition = newPosition;
				}
			}

            selectionEnd = max;

			document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, min.Y, max.Y));
			document.CommitUpdate();
			OnSelectionChanged(EventArgs.Empty);
		}

        //public void ExtendALTSelection(TextLocation oldPosition, TextLocation newPosition)
        //{
        //    if (oldPosition == newPosition)
        //        return;

        //    TextLocation min;
        //    TextLocation max;

        //    NewLineSelection selectionNew;

        //    int oldNewX = newPosition.X;

        //    bool oldIsGreater = GreaterEqPos(oldPosition, newPosition);
        //    if (oldIsGreater)
        //    {
        //        min = newPosition;
        //        max = oldPosition;
        //    }
        //    else
        //    {
        //        min = oldPosition;
        //        max = newPosition;
        //    }

        //    if (!HasSomethingSelected)
        //    {
        //        ClearWithoutUpdate();

        //        for (int i = 0; i <= (max.Line - min.Line); i++)
        //        {
        //            if (max.Column < min.Column)
        //            {
        //                max.Column = min.Column;
        //            }

        //            selectionNew = new NewLineSelection(document, new TextLocation(min.Column, min.Line + i),
        //                new TextLocation(max.Column, min.Line + i));

        //            this.selectionCollection.Add(selectionNew);
        //        }

        //        min = selectionCollection[0].StartPosition;
        //        //max = selectionCollection[selectionCollection.Count - 1].EndPosition;
        //        if (newPosition.Line > max.Line)
        //            max = newPosition;

        //        SelectionStart = oldPosition;
        //        SelectionEnd = newPosition;

        //        selectionMaxLine = 0;
        //        selectionMaxCloumn = 0;
        //    }
        //    else
        //    {
        //        ISelection selection = selectionCollection[0];

        //        if (selectFrom.where == WhereFrom.Gutter)
        //        {
        //            newPosition.X = 0;
        //        }

        //        newPosition.X = oldNewX;

        //        if (!selectionCollection.Exists(pos => pos.StartPosition.Line == newPosition.Line))
        //        {
        //            selectionNew = new NewLineSelection(document, new TextLocation(selection.StartPosition.Column, newPosition.Line),
        //                new TextLocation(selection.EndPosition.Column, newPosition.Line));
        //            selectionCollection.Add(selectionNew);
        //        }

        //        List<ISelection> deleteSelections = new List<ISelection>();

        //        for (int i = 0; i < selectionCollection.Count; i++)
        //        {
        //            if (selectionCollection[i].EndPosition.Line > textArea.Caret.Line)
        //                deleteSelections.Add(selectionCollection[i]);
        //            if (newPosition.Column >= selection.StartPosition.Column)
        //                selectionCollection[i].EndPosition = new TextLocation(newPosition.Column, selectionCollection[i].EndPosition.Line);
        //        }

        //        foreach (var removeItem in deleteSelections)
        //            selectionCollection.Remove(removeItem);

        //        min = selectionCollection[0].StartPosition;
        //        //max = selectionCollection[selectionCollection.Count - 1].EndPosition;
        //        if (newPosition.Line > max.Line)
        //            max = newPosition;

        //        if (newPosition.Column >= SelectionStart.Column)
        //            selectionEnd = new TextLocation(newPosition.Column, newPosition.Line);

        //    }

        //    if (newPosition.Line > selectionMaxLine)
        //        selectionMaxLine = newPosition.Line;
        //    if (newPosition.Column > selectionMaxCloumn)
        //        selectionMaxCloumn = newPosition.Column;

        //    document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, min.Y, max.Y));
        //    document.CommitUpdate();
        //    OnSelectionChanged(EventArgs.Empty);

        //}

        public void ExtendALTSelection()
        {
            int lineMin = selectionStart.Line;
            int lineMax = selectionEnd.Line;

            selectionCollection.Clear();

            for (int line = lineMin; line <= lineMax; line++)
            {
                if (selectionEnd.Column >= selectionStart.Column)
                {
                    NewLineSelection selectionNew = new NewLineSelection(document, new TextLocation(selectionStart.Column, line),
                      new TextLocation(selectionEnd.Column, line));
                    selectionCollection.Add(selectionNew);
                }
            }          

            document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, lineMin, lineMax));
            document.CommitUpdate();
            OnSelectionChanged(EventArgs.Empty);

        }

        public void PaintSelectionRange(Graphics g)
        {
            if (IsMutilSelect)
            {
                if (selectionCollection.Count > 0)
                {
                    DrawLineX1pos = textArea.TextView.DrawingPosition.X + textArea.TextView.GetDrawingXPos(SelectionStart.Line, SelectionStart.Column);
                    DrawLineX2pos = textArea.TextView.DrawingPosition.X + textArea.TextView.GetDrawingXPos(SelectionEnd.Line, SelectionEnd.Column);
                    DrawLineY1pos = textArea.TextView.DrawingPosition.Y +
                        (textArea.Document.GetVisibleLine(SelectionStart.Line)) * textArea.TextView.FontHeight -
                        textArea.TextView.TextArea.VirtualTop.Y;
                    DrawLineY2pos = textArea.Caret.ScreenPosition.Y + textArea.TextView.FontHeight - 1;
                    var selectionColor = textArea.Document.HighlightingStrategy.GetColorFor("Selection");                   

                    g.DrawLine(new Pen(selectionColor.BackgroundColor, 1.0f), new Point(DrawLineX1pos, DrawLineY1pos), new Point(DrawLineX1pos, DrawLineY2pos));

                    //g.DrawLine(new Pen(selectionColor.BackgroundColor, 1.0f), new Point(posX2, posY1), new Point(posX2, posY2));

                    g.FillRectangle(new SolidBrush(selectionColor.BackgroundColor), DrawLineX1pos, DrawLineY1pos, DrawLineX2pos - DrawLineX1pos + 1, DrawLineY2pos - DrawLineY1pos);

                }
            }
        }

        public void PaintSelectionRange(Graphics g, int lineNum, RectangleF lineRectangle)
        {
            if (IsMutilSelect)
            {
                if (selectionCollection.Count > 0)
                {
                    if (!selectionCollection.Exists(w => w.StartPosition.Line == lineNum))
                        return;

                    float posX1 = textArea.TextView.DrawingPosition.X + textArea.TextView.GetDrawingXPos(SelectionStart.Line, SelectionStart.Column);
                    float posX2 = textArea.TextView.DrawingPosition.X + textArea.TextView.GetDrawingXPos(SelectionEnd.Line, SelectionEnd.Column);
                    float posY1 = lineRectangle.Top;
                    float posY2 = lineRectangle.Top + lineRectangle.Height;
                    var selectionColor = textArea.Document.HighlightingStrategy.GetColorFor("Selection");

                    g.DrawLine(new Pen(selectionColor.BackgroundColor, 1.0f), new PointF(posX1, posY1), new PointF(posX1, posY2));

                    //g.DrawLine(new Pen(selectionColor.BackgroundColor, 1.0f), new Point(posX2, posY1), new Point(posX2, posY2));

                    g.FillRectangle(new SolidBrush(selectionColor.BackgroundColor), posX1, posY1, posX2 - posX1 + 1, posY2 - posY1);

                }
            }
        }

        // retrieve the next available line
        // - checks that there are more lines available after the current one
        // - if there are then the next line is returned
        // - if there are NOT then the last position on the given line is returned
        public TextLocation NextValidPosition(int line)
		{
			if (line < document.TotalNumberOfLines - 1)
				return new TextLocation(0, line + 1);
			else
				return new TextLocation(document.GetLineSegment(document.TotalNumberOfLines - 1).Length + 1, line);
		}

        internal void ClearWithoutUpdate()
        {
            while (selectionCollection.Count > 0)
            {
                ISelection selection = selectionCollection[selectionCollection.Count - 1];
                selectionCollection.RemoveAt(selectionCollection.Count - 1);
                document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, selection.StartPosition.Y, selection.EndPosition.Y));
                OnSelectionChanged(EventArgs.Empty);
            }          

            if (IsMutilSelect)
            {
                document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, selectionStart.Y, selectionEnd.Y));
            }

            DrawLineX1pos = 0;
            DrawLineX2pos = 0;
            DrawLineY1pos = 0;
            DrawLineY2pos = 0;

            selectionMaxCloumn = 0;
            selectionMaxLine = 0;

            if (IsMutilSelect)
            {
                selectionStart = new TextLocation();
                SelectionEnd = new TextLocation();
            }
        }

        /// <remarks>
        /// Clears the selection.
        /// </remarks>
        public void ClearSelection()
		{
			Point mousepos;
			mousepos = textArea.mousepos;
			// this is the most logical place to reset selection starting
			// positions because it is always called before a new selection
			selectFrom.first = selectFrom.where;
			TextLocation newSelectionStart = textArea.TextView.GetLogicalPosition(mousepos.X - textArea.TextView.DrawingPosition.X, mousepos.Y - textArea.TextView.DrawingPosition.Y);
			if (selectFrom.where == WhereFrom.Gutter) {
				newSelectionStart.X = 0;
//				selectionStart.Y = -1;
			}
			if (newSelectionStart.Line >= document.TotalNumberOfLines) {
				newSelectionStart.Line = document.TotalNumberOfLines-1;
				newSelectionStart.Column = document.GetLineSegment(document.TotalNumberOfLines-1).Length;
			}
			this.SelectionStart = newSelectionStart;

			ClearWithoutUpdate();
			document.CommitUpdate();
		}

        /// <summary>
        /// Clear MutilLine With Selected 
        /// </summary>
        public void ClearMutilSelect()
        {
            int lineMin = selectionStart.Line;
            int lineMax = selectionEnd.Line;
            selectionStart = textArea.Caret.Position;
            SelectionEnd = textArea.Caret.Position;
            document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, lineMin, lineMax));
            document.CommitUpdate();            
        }

        /// <remarks>
        /// Removes the selected text from the buffer and clears
        /// the selection.
        /// </remarks>
        public void RemoveSelectedText()
        {
            if (SelectionIsReadonly)
            {
                ClearSelection();
                return;
            }
            List<int> lines = new List<int>();
            int offset = -1;
            bool oneLine = true;
            //PriorityQueue queue = new PriorityQueue();
            foreach (ISelection s in selectionCollection)
            {
                if (s.IsCurrent)
                {
                    //ISelection s = ((ISelection)queue.Remove());
                    if (oneLine)
                    {
                        int lineBegin = s.StartPosition.Y;
                        if (lineBegin != s.EndPosition.Y)
                        {
                            oneLine = false;
                        }
                        else
                        {
                            lines.Add(lineBegin);
                        }
                    }
                    offset = s.Offset;
                    document.Remove(s.Offset, s.Length);
                    //queue.Insert(-s.Offset, s);
                }

                if (IsMutilSelect)
                {
                    s.EndPosition = new TextLocation(s.StartPosition.Column, s.StartPosition.Line);
                }

            }

            if (!IsMutilSelect)
            {
                ClearSelection();
            }
            else if (offset >= 0)
            {
                selectionEnd = new TextLocation(document.OffsetToPosition(offset).Column, selectionEnd.Line);
            }

            if (offset >= 0)
            {
                //             TODO:
                //document.Caret.Offset = offset;
                textArea.Caret.Position = document.OffsetToPosition(offset);
            }
            if (offset != -1)
            {
                if (oneLine)
                {
                    foreach (int line in lines)
                    {
                        document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, line));
                    }
                }
                else
                {
                    document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
                }
                document.CommitUpdate();
            }
        }
		
		/// <summary>
        /// 
        /// </summary>
        /// <param name="s1"></param>
        /// <param name="s2"></param>
        /// <returns></returns>
		bool SelectionsOverlap(ISelection s1, ISelection s2)
		{
			return (s1.Offset <= s2.Offset && s2.Offset <= s1.Offset + s1.Length)                         ||
				(s1.Offset <= s2.Offset + s2.Length && s2.Offset + s2.Length <= s1.Offset + s1.Length) ||
				(s1.Offset >= s2.Offset && s1.Offset + s1.Length <= s2.Offset + s2.Length);
		}
		
		/// <remarks>
		/// Returns true if the given offset points to a section which is
		/// selected.
		/// </remarks>
		public bool IsSelected(int offset)
		{
			return GetSelectionAt(offset) != null;
		}

		/// <remarks>
		/// Returns a <see cref="ISelection"/> object giving the selection in which
		/// the offset points to.
		/// </remarks>
		/// <returns>
		/// <code>null</code> if the offset doesn't point to a selection
		/// </returns>
		public ISelection GetSelectionAt(int offset)
		{
			foreach (ISelection s in selectionCollection) {
				if (s.ContainsOffset(offset)) {
					return s;
				}
			}
			return null;
		}
		
		/// <remarks>
		/// Used internally, do not call.
		/// </remarks>
		internal void Insert(int offset, string text)
		{
//			foreach (ISelection selection in SelectionCollection) {
//				if (selection.Offset > offset) {
//					selection.Offset += text.Length;
//				} else if (selection.Offset + selection.Length > offset) {
//					selection.Length += text.Length;
//				}
//			}
		}
		
		/// <remarks>
		/// Used internally, do not call.
		/// </remarks>
		internal void Remove(int offset, int length)
		{
//			foreach (ISelection selection in selectionCollection) {
//				if (selection.Offset > offset) {
//					selection.Offset -= length;
//				} else if (selection.Offset + selection.Length > offset) {
//					selection.Length -= length;
//				}
//			}
		}
		
		/// <remarks>
		/// Used internally, do not call.
		/// </remarks>
		internal void Replace(int offset, int length, string text)
		{
//			foreach (ISelection selection in selectionCollection) {
//				if (selection.Offset > offset) {
//					selection.Offset = selection.Offset - length + text.Length;
//				} else if (selection.Offset + selection.Length > offset) {
//					selection.Length = selection.Length - length + text.Length;
//				}
//			}
		}

        public List<ColumnRange> GetSelectionsAtLine(int lineNumber)
        {
            List<ColumnRange> selections = new List<ColumnRange>();

            foreach (ISelection selection in selectionCollection)
            {
                int startLine = selection.StartPosition.Y;
                int endLine = selection.EndPosition.Y;

                if (startLine < lineNumber && lineNumber < endLine)
                {
                    selections.Add(ColumnRange.WholeColumn);
                    break;
                }

                if (startLine == lineNumber && endLine == lineNumber)
                {
                    LineSegment line = document.GetLineSegment(startLine);
                    int startColumn = selection.StartPosition.X;
                    int endColumn = selection.EndPosition.X;
                    selections.Add(new ColumnRange(startColumn, endColumn, selection.IsCurrent));
                }

                else
                {
                    if (startLine == lineNumber)
                    {
                        LineSegment line = document.GetLineSegment(startLine);
                        int startColumn = selection.StartPosition.X;
                        int endColumn = line.Length + 1;
                        selections.Add(new ColumnRange(startColumn, endColumn, selection.IsCurrent));
                        break;
                    }

                    if (endLine == lineNumber)
                    {
                        int endColumn = selection.EndPosition.X;
                        selections.Add(new ColumnRange(0, endColumn, selection.IsCurrent));
                        break;
                    }
                }

            }          

            return selections;
        }

        public ColumnRange GetSelectionAtLine(int lineNumber)
		{
            foreach (ISelection selection in selectionCollection)
            {
                int startLine = selection.StartPosition.Y;
                int endLine = selection.EndPosition.Y;
                if (startLine < lineNumber && lineNumber < endLine)
                {
                    return ColumnRange.WholeColumn;
                }                

                if (startLine == lineNumber)
                {
                    LineSegment line = document.GetLineSegment(startLine);
                    int startColumn = selection.StartPosition.X;
                    int endColumn = endLine == lineNumber ? selection.EndPosition.X : line.Length + 1;
                    return new ColumnRange(startColumn, endColumn, selection.IsCurrent);
                }

                if (endLine == lineNumber)
                {
                    int endColumn = selection.EndPosition.X;
                    return new ColumnRange(0, endColumn, selection.IsCurrent);
                }
            }
			
			return ColumnRange.NoColumn;
		}
		
		public void FireSelectionChanged()
		{
			OnSelectionChanged(EventArgs.Empty);
		}
		protected virtual void OnSelectionChanged(EventArgs e)
		{
			if (SelectionChanged != null) {
				SelectionChanged(this, e);
			}
		}
		
		public event EventHandler SelectionChanged;
	}

	// selection initiated from...
	internal class SelectFrom {
		public int where = WhereFrom.None; // last selection initiator
		public int first = WhereFrom.None; // first selection initiator

		public SelectFrom()
		{
		}
	}

	// selection initiated from type...
	internal class WhereFrom {
		public const int None = 0;
		public const int Gutter = 1;
		public const int TArea = 2;
	}
}
